/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Haystack Software Inc. All rights reserved.
 *  Licensed under the PolyForm Strict License 1.0.0. See License.txt in the project root for
 *  license information.
 *--------------------------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See code-license.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { $, IDomNodePagePosition } from "vs/base/browser/dom"
import { IView, IViewSize } from "vs/base/browser/ui/grid/grid"
import { IBoundarySashes } from "vs/base/browser/ui/sash/sash"
import {
  DistributeSizing,
  ISplitViewStyles,
  IView as ISplitViewView,
  Orientation,
  SplitView,
} from "vs/base/browser/ui/splitview/splitview"
import { Color } from "vs/base/common/color"
import { Event } from "vs/base/common/event"
import { DisposableStore, IDisposable } from "vs/base/common/lifecycle"

export interface CenteredViewState {
  // width of the fixed centered layout
  targetWidth: number
  // proportional size of left margin
  leftMarginRatio: number
  // proportional size of right margin
  rightMarginRatio: number
}

const defaultState: CenteredViewState = {
  targetWidth: 900,
  leftMarginRatio: 0.1909,
  rightMarginRatio: 0.1909,
}

const distributeSizing: DistributeSizing = { type: "distribute" }

function createEmptyView(
  background: Color | undefined
): ISplitViewView<{ top: number; left: number }> {
  const element = $(".centered-layout-margin")
  element.style.height = "100%"
  if (background) {
    element.style.backgroundColor = background.toString()
  }

  return {
    element,
    layout: () => undefined,
    minimumSize: 60,
    maximumSize: Number.POSITIVE_INFINITY,
    onDidChange: Event.None,
  }
}

function toSplitViewView(
  view: IView,
  getHeight: () => number
): ISplitViewView<{ top: number; left: number }> {
  return {
    element: view.element,
    get maximumSize() {
      return view.maximumWidth
    },
    get minimumSize() {
      return view.minimumWidth
    },
    onDidChange: Event.map(view.onDidChange, (e) => e && e.width),
    layout: (size, offset, ctx) =>
      view.layout(size, getHeight(), ctx?.top ?? 0, (ctx?.left ?? 0) + offset),
  }
}

export interface ICenteredViewStyles extends ISplitViewStyles {
  background: Color
}

export class CenteredViewLayout implements IDisposable {
  private splitView?: SplitView<{ top: number; left: number }>
  private lastLayoutPosition: IDomNodePagePosition = {
    width: 0,
    height: 0,
    left: 0,
    top: 0,
  }
  private style!: ICenteredViewStyles
  private didLayout = false
  private emptyViews:
    | ISplitViewView<{ top: number; left: number }>[]
    | undefined
  private readonly splitViewDisposables = new DisposableStore()

  constructor(
    private container: HTMLElement,
    private view: IView,
    public state: CenteredViewState = { ...defaultState },
    private centeredLayoutFixedWidth: boolean = false
  ) {
    this.container.appendChild(this.view.element)
    // Make sure to hide the split view overflow like sashes #52892
    this.container.style.overflow = "hidden"
  }

  get minimumWidth(): number {
    return 220
  }
  get maximumWidth(): number {
    return this.splitView ? this.splitView.maximumSize : this.view.maximumWidth
  }
  get minimumHeight(): number {
    return this.view.minimumHeight
  }
  get maximumHeight(): number {
    return this.view.maximumHeight
  }
  get onDidChange(): Event<IViewSize | undefined> {
    return this.view.onDidChange
  }

  private _boundarySashes: IBoundarySashes = {}
  get boundarySashes(): IBoundarySashes {
    return this._boundarySashes
  }
  set boundarySashes(boundarySashes: IBoundarySashes) {
    this._boundarySashes = boundarySashes

    if (!this.splitView) {
      return
    }

    this.splitView.orthogonalStartSash = boundarySashes.top
    this.splitView.orthogonalEndSash = boundarySashes.bottom
  }

  layout(width: number, height: number, top: number, left: number): void {
    this.lastLayoutPosition = { width, height, top, left }
    if (this.splitView) {
      this.splitView.layout(width, this.lastLayoutPosition)
      if (!this.didLayout || this.centeredLayoutFixedWidth) {
        this.resizeSplitViews()
      }
    } else {
      this.view.layout(width, height, top, left)
    }

    this.didLayout = true
  }

  private resizeSplitViews(): void {
    if (!this.splitView) {
      return
    }
    if (this.centeredLayoutFixedWidth) {
      const centerViewWidth = Math.min(
        this.lastLayoutPosition.width,
        this.state.targetWidth
      )
      const marginWidthFloat =
        (this.lastLayoutPosition.width - centerViewWidth) / 2
      this.splitView.resizeView(0, Math.floor(marginWidthFloat))
      this.splitView.resizeView(1, centerViewWidth)
      this.splitView.resizeView(2, Math.ceil(marginWidthFloat))
    } else {
      const leftMargin =
        this.state.leftMarginRatio * this.lastLayoutPosition.width
      const rightMargin =
        this.state.rightMarginRatio * this.lastLayoutPosition.width
      const center = this.lastLayoutPosition.width - leftMargin - rightMargin
      this.splitView.resizeView(0, leftMargin)
      this.splitView.resizeView(1, center)
      this.splitView.resizeView(2, rightMargin)
    }
  }

  setFixedWidth(option: boolean) {
    this.centeredLayoutFixedWidth = option
    if (!!this.splitView) {
      this.updateState()
      this.resizeSplitViews()
    }
  }

  private updateState() {
    if (!!this.splitView) {
      this.state.targetWidth = this.splitView.getViewSize(1)
      this.state.leftMarginRatio =
        this.splitView.getViewSize(0) / this.lastLayoutPosition.width
      this.state.rightMarginRatio =
        this.splitView.getViewSize(2) / this.lastLayoutPosition.width
    }
  }

  isActive(): boolean {
    return !!this.splitView
  }

  styles(style: ICenteredViewStyles): void {
    this.style = style
    if (this.splitView && this.emptyViews) {
      this.splitView.style(this.style)
      this.emptyViews[0].element.style.backgroundColor =
        this.style.background.toString()
      this.emptyViews[1].element.style.backgroundColor =
        this.style.background.toString()
    }
  }

  activate(active: boolean): void {
    if (active === this.isActive()) {
      return
    }

    if (active) {
      this.container.removeChild(this.view.element)
      this.splitView = new SplitView(this.container, {
        inverseAltBehavior: true,
        orientation: Orientation.HORIZONTAL,
        styles: this.style,
      })
      this.splitView.orthogonalStartSash = this.boundarySashes.top
      this.splitView.orthogonalEndSash = this.boundarySashes.bottom

      this.splitViewDisposables.add(
        this.splitView.onDidSashChange(() => {
          if (!!this.splitView) {
            this.updateState()
          }
        })
      )
      this.splitViewDisposables.add(
        this.splitView.onDidSashReset(() => {
          this.state = { ...defaultState }
          this.resizeSplitViews()
        })
      )

      this.splitView.layout(
        this.lastLayoutPosition.width,
        this.lastLayoutPosition
      )
      const backgroundColor = this.style ? this.style.background : undefined
      this.emptyViews = [
        createEmptyView(backgroundColor),
        createEmptyView(backgroundColor),
      ]

      this.splitView.addView(this.emptyViews[0], distributeSizing, 0)
      this.splitView.addView(
        toSplitViewView(this.view, () => this.lastLayoutPosition.height),
        distributeSizing,
        1
      )
      this.splitView.addView(this.emptyViews[1], distributeSizing, 2)

      this.resizeSplitViews()
    } else {
      if (this.splitView) {
        this.container.removeChild(this.splitView.el)
      }
      this.splitViewDisposables.clear()
      this.splitView?.dispose()
      this.splitView = undefined
      this.emptyViews = undefined
      this.container.appendChild(this.view.element)
      this.view.layout(
        this.lastLayoutPosition.width,
        this.lastLayoutPosition.height,
        this.lastLayoutPosition.top,
        this.lastLayoutPosition.left
      )
    }
  }

  isDefault(state: CenteredViewState): boolean {
    if (this.centeredLayoutFixedWidth) {
      return state.targetWidth === defaultState.targetWidth
    } else {
      return (
        state.leftMarginRatio === defaultState.leftMarginRatio &&
        state.rightMarginRatio === defaultState.rightMarginRatio
      )
    }
  }

  dispose(): void {
    this.splitViewDisposables.dispose()

    if (this.splitView) {
      this.splitView.dispose()
      this.splitView = undefined
    }
  }
}
